package edu.unice.simul.modele.moteur;

import java.util.List;

import edu.unice.simul.modele.contenus.immobiles.Immobile;
import edu.unice.simul.modele.contenus.immobiles.Sortie;
import edu.unice.simul.modele.contenus.mobiles.Humain;

/**
 * Prend en compte l'environement proche d'un humain
 * 
 * pour déterminer la vitesse souhaitée adéquate
 * 
 * pour déterminer les forces de contacts.
 * 
 * Détermine et prend en compte le stress et les blessures.
 * 
 * @author runigo
 * 
 */

public class AjusteVitesseSouhaitee {
	private double distanceCarre, distance;
	private double produitScalaire, produitVectoriel;
	private double absRelative, ordRelative;
	private double produitScalaireUnitaire;
	private double courtoisieCarre, courtoisie;
	private double sommeProduit = 0.;
	private Humain humain;

	public AjusteVitesseSouhaitee() {
	}

	/**
	 * Ajuste la vitesse souhaitée en fonction de l'environement,
	 * 
	 * @param humain
	 */
	public void calcul(Humain humain) {
		this.humain = humain;
		this.sommeProduit = 0.;
		// Distance laissée par un humain devant lui, prend en compte le stress
		courtoisie = 1 + humain.getCourtoisie() * (100 - humain.getPrcStress())
				/ 100;
		double distanceMin = courtoisie;
		courtoisieCarre = Math.pow(courtoisie, 2);

		List<Humain> humains = humain.getPiece().getHumains();
		List<Immobile> obstacles = humain.getPiece().getImmobiles();

		// Effets des obstacles sur les humains
		for (Immobile o : obstacles) {
			if (!(o instanceof Sortie)) {
				// Calcul des coordonnées relative et de la distance
				absRelative = o.getAbscisse() - humain.getAbscisse();
				ordRelative = o.getOrdonnee() - humain.getOrdonnee();
				distanceCarre = (absRelative * absRelative + ordRelative * ordRelative);

				if (distanceCarre < 9) { // distance de vision des obstacles = 3
					appliquerForcesDeContactsObstacles();
					appliquerInfluencesObstacles();
				}
			}
		}

		// Ajuste la direction souhaitée en fonction des obstacles vus
		// razerMur exprime la propension à ne pas s'écarter des murs
		int razerMur = 10;
		if (sommeProduit < razerMur * 1.57)
			humain.setDirectionSouhaitee(humain.getDirectionSouhaitee()
					- sommeProduit / razerMur);
		else
			humain.setDirectionSouhaitee(humain.getDirectionSouhaitee() - Math.PI);

		// Effet des humains entre eux

		sommeProduit = 0; // Réinitialisation

		for (Humain h : humains)
			if (h != humain && !h.estSorti() && h.enVie()) {
				absRelative = h.getAbscisse() - humain.getAbscisse();
				ordRelative = h.getOrdonnee() - humain.getOrdonnee();
				distanceCarre = absRelative * absRelative + ordRelative * ordRelative;

				produitScalaire = (absRelative
						* Math.cos(humain.getDirection().getDirection()) + ordRelative
						* Math.sin(humain.getDirection().getDirection()));

				appliquerForcesContactsHumains();
				appliquerInfluencesHumains(distanceMin);
			}

		// Ajuste le module de la vitesse souhaitée en fonction des humains vus
		humain.setVitesseSouhaitee(humain.getVitesseMax() * distanceMin
				/ humain.getCourtoisie() * humain.getPrcVie() / 100);

		// Ajuste la direction souhaitée en fonction des humains vus
		// La sociabilité exprime la propension à ne pas s'écarter des autres
		int sociabilite = 3;
		if (sommeProduit < 15.7)
			humain.setDirectionSouhaitee(humain.getDirectionSouhaitee()
					- sommeProduit / sociabilite);
		else
			humain.setDirectionSouhaitee(humain.getDirectionSouhaitee() - Math.PI);
	}

	/**
	 * Calcul la déviation nécessaire pour doubler les humains, Détermine la
	 * distance devant l'humain pour ralentir si le dépassement est impossible
	 * 
	 * @param distanceMin
	 */
	private void appliquerInfluencesHumains(double distanceMin) {
		if (distanceCarre < courtoisieCarre) { // Humains proches
			if (produitScalaire > 0) { // Humains devant

				produitVectoriel = (ordRelative
						* Math.cos(humain.getDirection().getDirection()) - absRelative
						* Math.sin(humain.getDirection().getDirection()));
				distance = Math.sqrt(distanceCarre);
				produitScalaireUnitaire = produitScalaire / distance;
				if (produitScalaireUnitaire > 0.9) {
					if (distance < distanceMin)
						distanceMin = distance; // Pour ralentir
				}
				sommeProduit = sommeProduit + produitVectoriel / distanceCarre; // Pour
																																				// doubler
			}
		}
	}

	/**
	 * Calcul les forces de contact entre humains, Augmente le stress, Diminue le
	 * pourcentage de vie
	 */
	private void appliquerForcesContactsHumains() {
		if (distanceCarre < 2.0) {
			if (distanceCarre != 0) {
				humain.setForceContactX(humain.getForceContactX() - 200.0 * absRelative
						/ distanceCarre);
				humain.setForceContactY(humain.getForceContactY() - 200.0 * ordRelative
						/ distanceCarre);
			} else {
				if (!humain.estSorti())
					humain.diminuerVie(10);
			}
			if (distanceCarre < 1.0) {
				humain.setAugmenterStress(1.0 - distanceCarre); // Stress
				if (distanceCarre < 0.3)
					humain.diminuerVie(0.3 - distanceCarre); // Blessure
			}
		}
	}

	/**
	 * Calcul les forces de contacts des murs, Augmente le stress, Diminue le
	 * pourcentage de vie.
	 */
	private void appliquerForcesDeContactsObstacles() {
		if (distanceCarre < 0.5) {
			if (distanceCarre != 0) {
				humain.setForceContactX(humain.getForceContactX()
						- (100.0 * absRelative) / distanceCarre / distanceCarre);
				humain.setForceContactY(humain.getForceContactY()
						- (100.0 * ordRelative) / distanceCarre / distanceCarre);
			}
			if (distanceCarre < 0.7) {
				humain.setAugmenterStress(0.7 - distanceCarre); // Stress
				if (distanceCarre < 0.3)
					humain.diminuerVie(0.3 - distanceCarre); // Blessure
			}
		}
	}

	/**
	 * Calcul la déviation nécessaire pour éviter les obstacles
	 */
	private void appliquerInfluencesObstacles() {
		produitScalaire = (absRelative
				* Math.cos(humain.getDirection().getDirection()) + ordRelative
				* Math.sin(humain.getDirection().getDirection()));
		if (produitScalaire > 0) {
			produitVectoriel = (ordRelative
					* Math.cos(humain.getDirection().getDirection()) - absRelative
					* Math.sin(humain.getDirection().getDirection()));
			sommeProduit = sommeProduit + produitVectoriel * (1 + produitScalaire)
					/ distanceCarre;
		}
	}
}
